import { useMusic } from '@/store/useMusic'
import { useApi } from '@/api'
import { getFileType } from '@/utils/extRule'
import { FileType, GetNasFileListResponse, NasFile } from '@global/types'
import { reactive, watch, toRefs, Ref } from 'vue'
import { useRoute } from 'vue-router'
import { useMessage, useNotification } from '@/main'
import axios, { CancelTokenSource } from 'axios'
import { getItem, removeItem, setItem } from '@/utils/storage'
import { useI18n } from 'vue-i18n'
import { imgFile2Base64, compressBase64Img, base64ToFile, videoFile2Base64 } from '@/utils/thumbnail'
import loading from '@/utils/loading'
import { DebouncedFunc } from 'lodash'

export type SelectUploadFile = File & {
  path: string
  parentPath: string
}
interface FileSystemEntry {
  fullPath: string
  isFile: boolean
  isDirectory: boolean
}
type Upload = {
  file: SelectUploadFile
  fileInfo: NasFile
}
type Uploading = Upload & { source: CancelTokenSource }

const IS_ELECTRON = process.env.IS_ELECTRON
/** 允许同时上传的最大数量 */
const MAX_UPLOADS = 3
/** 上传文件分块大小 */
const CHUNK_SIZE = 1024 * 1024 * 2

type Props = {
  fileData: Ref<GetNasFileListResponse>
  uploadFileInputRef: Ref<HTMLInputElement>
  getFileListThrottle: DebouncedFunc<() => Promise<void>>
}

export default ({ fileData, uploadFileInputRef, getFileListThrottle }: Props) => {
  const { t } = useI18n()
  const route = useRoute()
  const api = useApi()
  const musci = useMusic()

  const state = reactive({
    /** 拖拽文件的容器是否显示 */
    dragWrapperShow: false,
    /** 选中的上传的文件列表 */
    selectUploadFileList: [] as SelectUploadFile[],
    /** 正在上传的文件 */
    uploadingList: [] as Uploading[],
    /** 等待上传的文件 */
    peddingList: [] as Upload[],
    /** 暂停上传的文件 */
    pauseList: [] as Upload[],
    /** 上传异常的文件 */
    errorList: [] as Upload[]
  })

  watch(
    //
    () => state.selectUploadFileList,
    async willUploadFileList => {
      for (const i in willUploadFileList) {
        const file = willUploadFileList[i]
        const filePath = file.parentPath + file.name
        const fileInfo: NasFile = await api.get('/nas/file', {
          loading: false,
          params: { filePath }
        })
        // 如果文件存在则断点续传
        if (fileInfo) {
          if (fileInfo.uploadFinish) return useMessage().warning(t('file-is-exist'))
          if (fileInfo!.fileSize !== file.size) return useMessage().error(t('file-is-abnormal'))
          return continueUpload({ fileInfo, file })
        }
        // 否则上传文件信息
        await startUpload(file)
      }
    }
  )

  /** 获取上传图标 */
  const getUploadIcon = (file: NasFile) => {
    const isUploading = state.uploadingList.some(item => item.fileInfo.id === file.id)
    if (isUploading) return 'fa-pause text-white'
    const isPending = state.peddingList.some(item => item.fileInfo.id === file.id)
    if (isPending) return 'fa-cloud-upload text-white'
    const isPause = state.pauseList.some(item => item.fileInfo.id === file.id)
    if (isPause) return 'fa-cloud-upload text-white'
    const isError = state.errorList.some(item => item.fileInfo.id === file.id)
    if (isError) return 'fa-times text-red-400'
    return 'fa-exclamation text-yellow-400'
  }

  /** 获取上传状态 */
  const getUploadStatus = (file: NasFile) => {
    const isSuccess = [...state.uploadingList, ...state.pauseList, ...state.peddingList].some(
      item => item.fileInfo.id === file.id
    )
    if (isSuccess) return 'success'
    const isError = state.errorList.some(item => item.fileInfo.id === file.id)
    if (isError) return 'error'
    return 'warning'
  }

  // 将拖拽文件对象转换文件列表
  const translateFileList = async (entryList: FileSystemEntry[], fileList: File[] = []) => {
    for (const i in entryList) {
      const item = entryList[i]
      // 如果是文件夹
      if (item.isDirectory) {
        const dirEntry = item as FileSystemEntry & { createReader: any }
        await new Promise(resolve => {
          dirEntry.createReader().readEntries(res => resolve(translateFileList(res, fileList)))
        })
      }
      // 如果是文件
      if (item.isFile) {
        const fileEntry = item as FileSystemEntry & { file: any }
        await new Promise(resolve => {
          fileEntry.file((file: SelectUploadFile) => {
            const match = fileEntry.fullPath.match(/.+(\/)/)
            const parentPath = match ? match[0].slice(1).replace(/\//g, '@@@') : ''
            file.parentPath = parentPath
            resolve(fileList.push(file))
          })
        })
      }
    }
    return fileList as SelectUploadFile[]
  }

  /** 获取拖拽的文件对象 */
  const getFilesByDrag = async (e: DragEvent) => {
    const parentPath = (route.query.parentPath as string) || ''
    state.dragWrapperShow = false
    const dataTransferItem = [...e.dataTransfer!.items].map(item => item.webkitGetAsEntry()) as FileSystemEntry[]
    const fileList = await translateFileList(dataTransferItem)
    state.selectUploadFileList = fileList.map(file => {
      file.parentPath = parentPath + file.parentPath
      return file
    })
  }
  /** 获取选中的文件对象 */
  const getFilesByClick = async (e: Event) => {
    const parentPath = (route.query.parentPath as string) || ''
    const target = e.target as HTMLInputElement
    state.selectUploadFileList = [...(target.files as unknown as SelectUploadFile[])].map(file => {
      file.parentPath = parentPath
      return file
    })
    target.value = ''
  }

  /** 上传文件 */
  const startUpload = async (file: SelectUploadFile) => {
    const form = new FormData()
    form.append('fileName', file.name)
    form.append('fileSize', file.size + '')
    form.append('parentPath', file.parentPath)
    const fileType = getFileType(file.name)
    form.append('fileType', fileType + '')
    // 生成缩略图
    if ([FileType.image, FileType.video].includes(fileType)) {
      // loading.add(`${file.name}：${t('generating-thumb')}`)
      try {
        let base64 = ''
        if (fileType === FileType.image) base64 = await imgFile2Base64(file)
        if (fileType === FileType.video) base64 = await videoFile2Base64(file)
        base64 = await compressBase64Img(base64, { maxWidth: 100, quality: 0.7 })
        const thumbnail = base64ToFile(base64, file.name)
        form.append('thumbnail', thumbnail)
      } catch (err) {
        form.set('fileType', FileType.other + '')
        useMessage().error(`${file.name}：${t('generate-thumb-error')}`)
      }
      // loading.remove()
    }
    // 上传文件信息
    const fileInfo: NasFile = await api.post('/nas/upload', form, { loading: false })
    if (IS_ELECTRON) setItem(`upload-${fileInfo.id}`, { ...fileInfo, path: file.path })
    if (state.uploadingList.length >= MAX_UPLOADS)
      state.peddingList.push({ file, fileInfo }) // 超过文件最大上传数量放入等待上传列表
    else continueUpload({ fileInfo, file }) // 否则开始断点续传
  }

  /** 上传正在等待的文件 */
  const uploadNextPendingFile = async () => {
    // 检测是否有等待上传的任务
    if (state.uploadingList.length >= MAX_UPLOADS) return
    const canUploadMoreNum = MAX_UPLOADS - state.uploadingList.length
    const pendingFileList = state.peddingList.splice(0, canUploadMoreNum)
    for (const i in pendingFileList) {
      const pendingFile = pendingFileList[i]
      if (pendingFile) await continueUpload(pendingFile)
    }
  }

  /** 断点续传 */
  const continueUpload = async ({ fileInfo, file }: { fileInfo: NasFile; file: SelectUploadFile }) => {
    // 更新文件信息
    const source = axios.CancelToken.source()
    const oldUploading = state.uploadingList.find(item => item.fileInfo.id === fileInfo.id)
    if (oldUploading) Object.assign(oldUploading, { source })
    else {
      state.uploadingList.push({
        file,
        fileInfo,
        source
      })
    }

    // 文件分块
    const start = fileInfo.uploaded as number
    const end = start + CHUNK_SIZE
    const chunk = file.slice(start, end)
    // 创建表单
    const form = new FormData()
    form.append('id', fileInfo.id + '')
    form.append('file', chunk)
    try {
      // 发起上传请求
      const newFileInfo: NasFile = await api.post('/nas/upload', form, {
        loading: false,
        cancelToken: source.token
      })
      const oldFileInfo = fileData.value.list.find(item => item.id === fileInfo.id)
      if (oldFileInfo) Object.assign(oldFileInfo, newFileInfo)
      getFileListThrottle()
      // 如果未上传完毕，继续断点续传
      if (!newFileInfo.uploadFinish) continueUpload({ fileInfo: newFileInfo, file })
      // 如果已上传完毕
      else {
        state.uploadingList = state.uploadingList.filter(item => item.fileInfo.id !== fileInfo.id)
        // 客户端删除文件的缓存信息
        if (IS_ELECTRON) removeItem(`upload-${fileInfo.id}`)
        await uploadNextPendingFile()
        if (newFileInfo.fileType === FileType.audio) musci.updateMusicList()
      }
    } catch (err: any) {
      if (err.message === '取消请求') return
      const errorFile = state.uploadingList.find(item => item.fileInfo.id === fileInfo.id) as Upload
      state.errorList.push(errorFile)
      state.uploadingList = state.uploadingList.filter(item => item.fileInfo.id !== errorFile.fileInfo.id)
      await uploadNextPendingFile()
    }
  }

  /** 如果正在上传的文件数量超过限制，将第一个正在上传的任务推入等待上传列表 */
  const uploadingPushPending = () => {
    if (state.uploadingList.length >= MAX_UPLOADS) {
      const [willPendingFile] = state.uploadingList.splice(0, 1)
      willPendingFile.source.cancel(JSON.stringify({ loading: false }))
      state.peddingList.push(willPendingFile)
    }
  }

  // 上传/暂停任务
  const changeUploadState = async (fileInfo: NasFile) => {
    // 将正在上传的任务暂停
    const uploadingFile = state.uploadingList.find(item => item.fileInfo.id === fileInfo.id)
    if (uploadingFile) {
      uploadingFile.source.cancel(JSON.stringify({ loading: false }))
      state.pauseList.push(uploadingFile)
      state.uploadingList = state.uploadingList.filter(item => item.fileInfo.id !== fileInfo.id)
      return
    }

    // 上传正在等待或已经暂停的任务
    const pauseFile = [...state.pauseList, ...state.peddingList, ...state.errorList].find(
      item => item.fileInfo.id === fileInfo.id
    )
    if (pauseFile) {
      uploadingPushPending()
      return continueUpload(pauseFile)
    }

    // 如果是客户端
    if (IS_ELECTRON) {
      // 获取文件信息缓存
      const fileCache = getItem(`upload-${fileInfo.id}`) as NasFile & { path: string }
      if (!fileCache) return uploadFileInputRef.value.click() // 如果获取不到则弹出文件选择框

      // 导入 fs 模块
      const { stat, createReadStream } = await import('fs-extra')
      await stat(fileCache.path)
        .then(res => {
          const isFile = res.isFile()
          if (!isFile) {
            uploadFileInputRef.value.click()
            removeItem(`upload-${fileInfo.id}`)
            Promise.reject('该路径的资源不是文件，请重新上传')
          }
        })
        .catch(() => {
          uploadFileInputRef.value.click()
          removeItem(`upload-${fileInfo.id}`)
          Promise.reject('寻找不到文件，请重新上传')
        })

      loading.add(t('reading-file'))
      // 读取文件流
      const readStream = createReadStream(fileCache.path, {
        highWaterMark: CHUNK_SIZE
      })
      const data: Blob[] = []
      readStream.on('data', buffer => data.push(new Blob([buffer])))
      readStream.on('error', () => {
        loading.remove()
        removeItem(`upload-${fileInfo.id}`)
        useNotification().error({
          title: t('read-file-error'),
          content: `${t('please-upload-file-again')}：${fileCache.fileName}`
        })
      })
      readStream.on('end', () => {
        loading.remove()
        const uploading = state.uploadingList.find(item => item.fileInfo.id === fileCache.id)
        if (uploading) Object.assign(uploading, { file: new Blob(data) as SelectUploadFile })
        uploadingPushPending()
        continueUpload({
          fileInfo: fileCache,
          file: new Blob(data) as SelectUploadFile
        })
      })
      return
    }

    // 所有列表都无该文件，则弹出选择文件框
    uploadFileInputRef.value.click()
  }

  return {
    ...toRefs(state),
    /** 获取 icon 图标 */
    getUploadIcon,
    /** 获取上传状态 */
    getUploadStatus,
    /** 获取拖拽的文件对象 */
    getFilesByDrag,
    /** 获取点击选中的文件对象 */
    getFilesByClick,
    /** 上传/暂停任务 */
    changeUploadState
  }
}
